package com.riteny.mybatis.generate;

import org.apache.commons.text.CaseUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Riteny
 * 2021/1/8  11:49
 */
public class GeneratorService {

    public static final String baseMapperPath = "com.riteny.mybatis.scanner.BaseMapper";
    public static final String baseServicePath = "com.riteny.mybatis.scanner.BaseService";
    public static final String criteriaPath = "com.riteny.mybatis.scanner.SearchCriteria";

    private static void generateRepositoryFile(String packageName, String outPutFolder, String tableName, String driveClass, String jdbcUrl, String username, String password) {

        //step1 獲取對應表的列信息
        Table table = getTableDetail(tableName, driveClass, jdbcUrl, username, password);

        //step2 創建目標文件夾
        createDirectory(outPutFolder);

        //step3 根據對應的列信息生成實體類
        writeJavaEntityClass(packageName, outPutFolder, table);

        //step4 根據對應的列信息生成搜索類
        writeJavaSearchCriteriaClass(packageName, outPutFolder, table);

        //step5 生成mapper文件
        writeMapperClass(packageName, outPutFolder, table);

        //step6 生成mapper.xml文件
        writeMapperXML(packageName, outPutFolder, table);

        //step7 生成service文件
        writeDbServiceClass(packageName, outPutFolder, table);
    }

    private static void generateRepositoryFiles(String packageName, String outPutFolder, String driveClass, String jdbcUrl, String username, String password) {

        //step1 獲取對應表的列信息
        List<String> tableNames = getTableNames(driveClass, jdbcUrl, username, password);

        for (String tableName : tableNames) {
            generateRepositoryFile(packageName, outPutFolder, tableName, driveClass, jdbcUrl, username, password);
        }
    }

    private static void createDirectory(String outPutFolder) {
        File file = new File(outPutFolder + "entity//");
        if (!file.exists()) {
            file.mkdir();
        }

        file = new File(outPutFolder + "repository//");
        if (!file.exists()) {
            file.mkdir();
        }

        file = new File(outPutFolder + "mapper//");
        if (!file.exists()) {
            file.mkdir();
        }
    }

    private static Table getTableDetail(String tableName, String driveClass, String jdbcUrl, String username, String password) {

        try (Connection connection = CommonConnectionFactory.getConnection(driveClass, jdbcUrl, username, password);
             PreparedStatement pst = connection.prepareStatement("select * from " + tableName + " where 1=2")) {

            ResultSetMetaData rsd = pst.executeQuery().getMetaData();

            Table table = new Table();
            List<Column> columns = new ArrayList<>();
            for (int i = 0; i < rsd.getColumnCount(); i++) {
                Column column = new Column();
                column.setColumnName(rsd.getColumnName(i + 1));
                column.setColumnType(rsd.getColumnClassName(i + 1));
                columns.add(column);
            }
            table.setTableName(tableName);
            table.setColumns(columns);

            return table;
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

    private static List<String> getTableNames(String driveClass, String jdbcUrl, String username, String password) {

        List<String> tables = new ArrayList<>();

        try (Connection connection = CommonConnectionFactory.getConnection(driveClass, jdbcUrl, username, password);
             PreparedStatement pst = connection.prepareStatement("show tables");
             ResultSet resultSet = pst.executeQuery();) {

            while (resultSet.next()) {
                tables.add(resultSet.getString(1));
            }

            return tables;
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void writeJavaEntityClass(String packageName, String outPutFolder, Table table) {

        String entityName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'});

        try (FileWriter writer = new FileWriter(outPutFolder + "entity//" + entityName + ".java", true)) {

            //step1 輸出包名
            writer.write("package " + packageName + ".entity;\n");
            writer.write("\r\n");

            //step2 輸出依賴項
            writer.write("import lombok.Data;\n");
            writer.write("\r\n");
            List<String> columnTypes = table.getColumns().stream().map(Column::getColumnType).distinct().collect(Collectors.toList());
            for (String columnType : columnTypes) {
                writer.write("import " + columnType + ";\n");
            }
            writer.write("\r\n");

            //step3 輸出類結構
            //step3.1 輸出類名
            writer.write("@Data\n");
            writer.write("public class " + entityName + "{\n");
            writer.write("\r\n");

            //step3.2 輸出字段
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                Class clazz = Class.forName(column.getColumnType());
                writer.write("    private " + clazz.getSimpleName() + " " + columnName + ";\n");
                writer.write("\r\n");
            }

            writer.write("}");

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void writeJavaSearchCriteriaClass(String packageName, String outPutFolder, Table table) {

        String entityName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'}) + "SearchCriteria";

        try (FileWriter writer = new FileWriter(outPutFolder + "entity//" + entityName + ".java", true)) {

            //step1 輸出包名
            writer.write("package " + packageName + ".entity;\n");
            writer.write("\r\n");

            //step2 輸出依賴項
            writer.write("import lombok.Data;\n");
            writer.write("import " + criteriaPath + ";\n");
            writer.write("\r\n");
            List<String> columnTypes = table.getColumns().stream().map(Column::getColumnType).distinct().collect(Collectors.toList());
            for (String columnType : columnTypes) {
                writer.write("import " + columnType + ";\n");
            }
            writer.write("\r\n");

            //step3 輸出類結構
            //step3.1 輸出類名
            writer.write("@Data\n");
            writer.write("public class " + entityName + " implements SearchCriteria{\n");
            writer.write("\r\n");

            //step3.2 輸出字段
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                Class clazz = Class.forName(column.getColumnType());
                writer.write("    private " + clazz.getSimpleName() + " " + columnName + ";\n");
                writer.write("\r\n");
            }

            writer.write("}");

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void writeMapperClass(String packageName, String outPutFolder, Table table) {

        String mapperName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'}) + "Mapper";
        String entityName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'});

        try (FileWriter writer = new FileWriter(outPutFolder + "repository//" + mapperName + ".java", true)) {
            //step1 輸出包名
            writer.write("package " + packageName + ".repository;\n");
            writer.write("\r\n");

            //step2 輸出依賴項
            writer.write("import " + baseMapperPath + ";\n");
            writer.write("import " + packageName + ".entity." + entityName + ";\n");
            writer.write("\r\n");

            //step3 輸出類結構
            //step3.1 輸出類名
            writer.write("public interface " + mapperName + " extends BaseMapper<Long," + entityName + ">{\n");
            writer.write("\r\n");

            writer.write("}");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void writeDbServiceClass(String packageName, String outPutFolder, Table table) {

        String dbServiceName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'}) + "DbService";
        String mapperName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'}) + "Mapper";

        try (FileWriter writer = new FileWriter(outPutFolder + "repository//" + dbServiceName + ".java", true)) {
            //step1 輸出包名
            writer.write("package " + packageName + ".repository;\n");
            writer.write("\r\n");

            //step2 輸出依賴項
            writer.write("import " + baseServicePath + ";\n");
            writer.write("\r\n");

            //step3 輸出類結構
            //step3.1 輸出類名
            writer.write("public class " + dbServiceName + " extends BaseService<" + mapperName + ">{\n");
            writer.write("\r\n");

            writer.write("    public CasCoreLaneDbService(" + mapperName + " repository) {\n");
            writer.write("        super(repository);\n");
            writer.write("    }\n");
            writer.write("}");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void writeMapperXML(String packageName, String outPutFolder, Table table) {

        String mapperName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'}) + "Mapper";
        String entityName = CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'});

        String mapperFullName = packageName + ".repository." + CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'}) + "Mapper";
        String entityFullName = packageName + ".entity." + CaseUtils.toCamelCase(table.getTableName(), true, new char[]{'_'});

        try (FileWriter writer = new FileWriter(outPutFolder + "mapper//" + mapperName + ".xml", true)) {
            //step1 輸出包名
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n");
            writer.write("<!DOCTYPE mapper PUBLIC \"-//ibatis.apache.org//DTD Mapper 3.0//EN\"\r\n" +
                    "        \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\r\n");
            writer.write("<mapper namespace=\"" + mapperFullName + "\">\n");

            writer.write("    <resultMap type=\"" + entityFullName + "\" id=\"" + entityName + "Map\">\n");
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                writer.write("        <result property=\"" + columnName + "\" column=\"" + column.getColumnName() + "\"/>\n");
            }
            writer.write("    </resultMap>\n");


            writer.write("    <sql id=\"allResult\">\n");
            writer.write("        <trim suffixOverrides=\",\">\n");
            for (Column column : table.getColumns()) {
                writer.write("            `" + column.getColumnName() + "`,\n");
            }
            writer.write("        </trim>\n");
            writer.write("     </sql>\n");


            writer.write("    <insert id=\"insert\">\n");
            writer.write("        INSERT INTO " + table.getTableName());
            writer.write("        <trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">\n");
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                writer.write("            <if test=\"" + columnName + "!=null and " + columnName + "!=''\">,\n");
                writer.write("                 `" + column.getColumnName() + "`,\n");
                writer.write("            </if>\n");
            }
            writer.write("        </trim>\n");


            writer.write("        <trim prefix=\"values (\" suffix=\")\" suffixOverrides=\",\">\n");
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                writer.write("            <if test=\"" + columnName + "!=null and " + columnName + "!=''\">,\n");
                writer.write("                 #{" + columnName + "},\n");
                writer.write("            </if>\n");
            }
            writer.write("        </trim>\n");
            writer.write("    </insert>\n");


            writer.write("    <select id=\"selectBySearchCriteria\" resultMap=\"" + entityName + "Map\">\n");
            writer.write("        select <include refid=\"allResult\"></include> from " + table.getTableName() + "\n");
            writer.write("        <trim prefix=\"where\" suffixOverrides=\"and | or\">\n");
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                writer.write("            <if test=\"" + columnName + "!=null and " + columnName + "!=''\">,\n");
                writer.write("                 `" + column.getColumnName() + "` = #{" + columnName + "} and \n");
                writer.write("            </if>\n");
            }
            writer.write("        </trim>\n");
            writer.write("    </select>\n");


            writer.write("    <update id=\"update\">\n");
            writer.write("        UPDATE lpr_alter_history\n");
            writer.write("        SET\n");
            writer.write("        <trim suffixOverrides=\",\">\n");
            for (Column column : table.getColumns()) {
                String columnName = CaseUtils.toCamelCase(column.getColumnName(), false, new char[]{'_'});
                writer.write("            <if test=\"" + columnName + "!=null and " + columnName + "!=''\">,\n");
                writer.write("                 `" + column.getColumnName() + "` = #{" + columnName + "} , \n");
                writer.write("            </if>\n");
            }
            writer.write("        </trim>\n");
            writer.write("         WHERE\n");
            writer.write("         <trim suffixOverrides=\"and\">\n");
            writer.write("             `id` = #{id} and\n");
            writer.write("        </trim>\n");
            writer.write("    </update>\n");

            writer.write("</mapper>\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
